package main

import (
	"fmt"
	"os"
	"strings"
	"regexp"
	"bufio"
	"strconv"
	"path/filepath"
)

const (
	CONFIG_DIRECTORY_DEFAULT string = "~/.config/strlstloltest/it/can/do/more/nesting/"
	CONFIG_FILENAME_DEFAULT string = "strlst"
	CONFIG_EXAMPLE string = `# this is an example configuration with a semantic placeholder in each field
# lines beginning with a number sign are comments
# %s is used as a (necessary) placeholder for the url
url_fmt=https://%s/path/to/files/
# accessible ip or domain of webserver
url=somewhere.someplace
# SSH information is used for file transfer purposes
ssh_port=22
ssh_user=strlst
# path to the identity file for automatic transfer
ssh_identity_path=~/.ssh/copyservice_rsa
# destination directory on the remote host
scp_directory=~/example/target/directory
secret=examplesecrettobekeptsafe
`
)

type Configuration struct {
	// specifies where to look, and what HTTP structure to insert url in
	url_fmt, url string
	// ssh information used for copy service, specifies port to use
	// and identity file
	ssh_user, ssh_identity_path, scp_directory string
	ssh_port int
	// specifies encryption secret
	secret string
}

func GetHomeDir() string {
	// query home directory
	home, err := os.UserHomeDir()
	if err != nil {
		fmt.Fprintf(os.Stderr, "fatal error: user home directory could not be determined\n")
		os.Exit(EXIT_HOME_INVALID)
	}

	return home
}

func GetConfigDefaultPath() string {
	return filepath.Join(GetHomeDir(), ".config", "strlst", "strlst")
}

func ConfigurationToString(config *Configuration, obfuscate_secret bool) string {
	secret := "***"
	if !obfuscate_secret {
		secret = config.secret
	}

	return fmt.Sprintf(
		"configuration { url_fmt = %s, url = %s, ssh_user = %s, ssh_user = %v, ssh_identity_path = %s, scp_directory = %s, secret = %s }",
		config.url_fmt,
		config.url,
		config.ssh_user,
		config.ssh_port,
		config.ssh_identity_path,
		config.scp_directory,
		secret,
	)
}

func GetConfiguration(program *string, path *string) Configuration {
	config := Configuration { ssh_port: -1 }

	// open file for reading
	configuration_file, err := os.Open(*path)
	// if file does not exist
	if os.IsNotExist(err) {
		fmt.Fprintf(os.Stderr, "%s: specified configuration file does not exist\n", *program)
		os.Exit(EXIT_SPECIFIED_PATH_INVALID)
	}
	defer configuration_file.Close()

	// process configuration file line for line
	matchers := [...](*regexp.Regexp) {
		regexp.MustCompile("^#.*$"),
		regexp.MustCompile("^url_fmt *= *([^ ].*)$"),
		regexp.MustCompile("^url *= *([^ ].*)$"),
		regexp.MustCompile("^ssh_user *= *([^ ].*)$"),
		regexp.MustCompile("^ssh_port *= *([0-9]+)$"),
		regexp.MustCompile("^ssh_identity_path *= *([^ ].*)$"),
		regexp.MustCompile("^scp_directory *= *([^ ].*)$"),
		regexp.MustCompile("^secret *= *([^ ].*)$"),
	}

	// create scanner to iterate file contents
	scanner := bufio.NewScanner(configuration_file)
	// iterate file contents
	for scanner.Scan() {
		line := scanner.Text()

		// match line against matchers to parse contents
		for index, matcher := range matchers {
			if matcher.MatchString(line) {
				// comment, skip
				if index == 0 { continue }

				// find submatch (singular) for anything that isn't a comment
				submatches := matchers[index].FindAllStringSubmatch(line, 1)
				if len(submatches) < 1 || len(submatches[0]) < 2 {
					fmt.Fprintf(os.Stderr, "%s: error matching at line: %s\n", *program, line)
					continue
				}
				submatch := submatches[0][1]

				// ugly solution, maybe use maps instead
				switch index {
				case 1: config.url_fmt = submatch
				case 2: config.url = submatch
				case 3: config.ssh_user = submatch
				case 4:
					value, err := strconv.Atoi(submatch)
					if err != nil {
						fmt.Fprintf(os.Stderr, "%s: error parsing port %s\n", *program, submatch)
						os.Exit(EXIT_CONFIG_INVALID)
					}
					config.ssh_port = value
				case 5: config.ssh_identity_path = submatch
				case 6: config.scp_directory = submatch
				case 7: config.secret = submatch
				}
				break
			}
		}
	}

	return config
}

func IsConfigurationValid(config *Configuration) bool {
	// very basic validation
	return strings.Contains(config.url_fmt, "http") &&
		strings.Contains(config.url, ".") &&
		config.ssh_user != "" &&
		config.ssh_port > 0 &&
		strings.Contains(config.ssh_identity_path, "/") &&
		strings.Contains(config.scp_directory, "/") &&
		config.secret != ""
}